package cc.rboot.limit.aspect;

import cc.rboot.limit.annotation.AccessLimit;
import cc.rboot._core.helper.AnnotationHelper;
import cc.rboot._core.helper.MethodHelper;
import cc.rboot.limit.core.IAccessLimitGrouper;
import cc.rboot.limit.error.AccessLimitException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Aspect
@Component
public class AccessLimitAspect {

    private static final ConcurrentHashMap<String, List<LocalDateTime>> LOCAL_LIMIT_COUNTER = new ConcurrentHashMap<String, List<LocalDateTime>>();

    @Pointcut("@annotation(cc.rboot.limit.annotation.AccessLimit)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        AccessLimit accessLimit = AnnotationHelper.get(joinPoint, AccessLimit.class);
        int seconds = accessLimit.time();
        int maxCount = accessLimit.limit();
        Class<? extends IAccessLimitGrouper> grouper = accessLimit.grouper();

        String mapKey = grouper.newInstance().grouper(joinPoint);

        if (mapKey == null) {
            return true;
        }

        LocalDateTime nowAt = LocalDateTime.now();
        LocalDateTime expiredAt = nowAt.plusSeconds(seconds);

        List<LocalDateTime> expiredAtList = LOCAL_LIMIT_COUNTER.get(mapKey);
        if (null == expiredAtList) {
            expiredAtList = new LinkedList<>();
        }
        expiredAtList.add(expiredAt);

        expiredAtList = expiredAtList.stream()
                .filter(time -> time.isAfter(nowAt))
                .collect(Collectors.toList());


        LOCAL_LIMIT_COUNTER.put(mapKey, expiredAtList);

        if (expiredAtList.size() > maxCount) {
            String degrade = accessLimit.degrade();
            /**
             * 有降级处理就降级处理，没有降级处理就报错
             */
            if (StringUtils.hasLength(degrade)) {
                try {
                    Method method = MethodHelper.get(joinPoint, degrade);
                    return method.invoke(joinPoint.getTarget(), joinPoint.getArgs());
                } catch (Exception ex) {
                    // Do nothing
                }
            }

            throw new AccessLimitException();
        }

        return joinPoint.proceed();
    }
}
